-- 集群节点管理中心服服务封装
-- auth:diandian
-- date:2018/9/27
--


local skynet = require "skynet.manager"
local cluster = require "skynet.cluster"
local ex_log = require "ex_log"
local NOT_ERR = false

local _nodemap = {}
local _hostmap = {}
local _typemap = {}
local RTIME_OUT = 25
local CMD = {}

local function notify_register(node)
    for k, v in pairs(_nodemap) do
        if node.name ~= k and v.followmap[node.type] then
            ex_log.info("notify_register:", node.name, k)
            cluster.send(k, ".ex_cluster_node", "on_register_node", {
                type = node.type,
                name = node.name,
                host = node.host,
            })
        end
    end
end

local function notify_delete(node)
    ex_log.warning("notify_delete:", node.name, node.host)
    for k, v in pairs(_nodemap) do
        if name ~= k and v.followmap[node.type] then
            cluster.send(k, ".ex_cluster_node", "on_delete_node", node.name)
        end
    end
end

local function reload_cluster()
    local map = {}
    for k, v in pairs(_nodemap) do
        map[k] = v.host
    end
    cluster.reload(map)
end

local function gen_type_name(typectrl)
    local name
    while true do
        local index = typectrl.index + 1
        typectrl.index = index
        name = string.format("%s_node%03d", typectrl.type, index)
        if not _nodemap[name] then
            break
        end
    end
    return name
end

function CMD.register_node(
    type_, name, host, node_followmap, all_followmap)
    followmap = followmap or {}
    local typectrl = _typemap[type_]
    if not typectrl then
        typectrl = {
            index = 0,
            type = type_,
        }
        _typemap[type_] = typectrl
    end
    local exist = _hostmap[host]
    if exist then
        if not name then
            --name = exist.name
        else
            _hostmap[host] = nil
        end
    end

    if not name then
        name = gen_type_name(typectrl)
    end
    local node = _nodemap[name]
    if not node then
        node = {}
        _nodemap[name] = node
        _hostmap[host] = node
    elseif not exist then
        ex_log.error("node was already exist in map", type_, tostring(name), host)
        return "node name already exist"
    end
    node.type = type_
    node.name = name
    node.host = host
    node.followmap = node_followmap
    node.rtime = os.time()
    reload_cluster()
    notify_register(node)
    local follow_nodemap = {}
    for k, v in pairs(_nodemap) do
        if all_followmap[v.type] then
            follow_nodemap[k] = {
                type = v.type,
                name = v.name,
                host = v.host,
            }
        end
    end
    return NOT_ERR, node, follow_nodemap
end

function CMD.heartbeat(node_)
    local node = _nodemap[node_.name]
    if not node then
        return CMD.register_node(node_.type, node_.name, node_.host, node_.followmap)
    end
    node.rtime = os.time()
    return NOT_ERR
end

local function check_heartbeat()
    skynet.timeout(200, check_heartbeat)
    local now = os.time()
    local to = {}
    for k, v in pairs(_nodemap) do
        if (now - v.rtime) > RTIME_OUT then
            table.insert(to, v)
        end
    end
    for i, v in ipairs(to) do
        _nodemap[v.name] = nil
    end

    for i, v in ipairs(to) do
        notify_delete(v)
    end
end

function CMD.start(host)
    assert(host)
    ex_log.trace("ex_cluster_center open ", host)
    local t = {
        ["center_node"] = host,
    }
    cluster.reload(t)
    cluster.open("center_node")
    skynet.timeout(1500, check_heartbeat)
end

skynet.start(function()
    skynet.register(".ex_cluster_center")
    skynet.dispatch("lua", function(session, source, cmd, ...)
        local f = CMD[cmd]
        assert(f, cmd)
        skynet.retpack(f(...))
    end)
end)
